package com.fast.gateway.filter;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.fast.common.constant.AuthNameConstant;
import com.fast.common.pojo.AuthUser;
import com.fast.common.redis.RedisClient;
import com.fast.common.utils.AuthUtils;
import com.fast.gateway.config.AuthConfigurationProperties;
import com.fast.gateway.exception.NotAuthException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Component
@EnableConfigurationProperties(AuthConfigurationProperties.class)
public class AuthenticationFilter implements GlobalFilter, InitializingBean, Ordered {

    private List<PathPattern> pathPatterns;
    private ObjectMapper objectMapper = new ObjectMapper();

    private final AuthConfigurationProperties properties;
    @Resource
    private AuthUtils authUtils;
    @Resource
    private RedisClient redisClient;
    public static final String X_CLIENT_TOKEN = "x-client-token";

    public AuthenticationFilter(AuthConfigurationProperties properties) {
        this.properties = properties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //判断是否是是认证的地址,不是就放过
        if (!match(exchange.getRequest().getPath().value())) {
            return chain.filter(exchange);
        }
        List<String> authorizations = exchange.getRequest().getHeaders().get(AuthNameConstant.AUTHORIZATION);
        if (CollectionUtils.isEmpty(authorizations)) {
            throw new NotAuthException("非法登录");
        }
        String token = authorizations.get(0);
        Claims claimsFromToken = authUtils.getClaimsFromToken(token);
        if (Objects.isNull(claimsFromToken)) {
            throw new NotAuthException("非法登录");
        }
        String AutoKey = authUtils.getAccessTokenKey(claimsFromToken.getId());
        AuthUser authUser = (AuthUser) redisClient.get(AutoKey);
        if (authUser == null) {
            throw new NotAuthException("未登录");
        }
        if (!authUser.getToken().equals(token)) {
            throw new NotAuthException("账号已其他地方登录");
        }
        ServerHttpRequest request = exchange.getRequest();
        //加时
        redisClient.expire(AutoKey, authUtils.getExpiration(), TimeUnit.SECONDS);

        ServerHttpRequest.Builder builder = request.mutate();
        // 原始jwt token
        try {
            builder.header(X_CLIENT_TOKEN, objectMapper.writeValueAsString(authUser));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return chain.filter(exchange.mutate().request(builder.build()).build());
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        List<String> excludeUrls = properties.getIgnoreUrls();
        if (excludeUrls == null || excludeUrls.isEmpty()) {
            pathPatterns = new ArrayList<>();
        } else {
            pathPatterns = excludeUrls.stream().map(PathPatternParser.defaultInstance::parse).sorted().collect(Collectors.toList());
        }
    }

    private boolean match(String path) {
        if (pathPatterns == null || pathPatterns.isEmpty()) {
            return true;
        }
        PathContainer pathContainer = PathContainer.parsePath(path);
        for (PathPattern pattern : pathPatterns) {
            if (pattern.matches(pathContainer)) {
                return false;
            }
        }
        return true;
    }


    @Override
    public int getOrder() {
        return -1;
    }
}